﻿using System;
using System.Collections.Generic;
using System.Linq;
using TfsMerge.Interface;
using TfsMerge.BO;
using Microsoft.TeamFoundation.VersionControl.Client;
using System.Windows.Forms;
using TfsMerge.Utils;

namespace TfsMerge.Controller
{
    public class MainController
    {
        public ITfs Tfs { get; private set; }

        public MainController(ITfs tfs)
        {
            Tfs = tfs;            
        }

        public IList<UIChangeset> GetMergeCandidates(string source, string target)
        {
            if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(target))
                return new List<UIChangeset>();

            var list = Tfs.GetMergeCandidates(source, target)
                .Select(x => new UIChangeset(x.Changeset)).ToList();

            return list;
        }

        public IList<UIChangeset> GetHistory(string source)
        {
            var list = Tfs.GetHistory(source)
                .Select(x => new UIChangeset(x)).ToList();

            return list;
        }

        public IEnumerable<UIBranchObject> GetBranches()
        {
            return Tfs.GetBranches()
                .Select(x => new UIBranchObject(x))
                .OrderBy(x => x.Name)
                .ToList();
        }


        public IEnumerable<int> CheckMerge(string source, string target, UIChangeset[] candidates)
        {
            if (!candidates.Any())
                throw new ArgumentException("CheckMerge: candidates.Count = 0");

            foreach (var itm in candidates)
            {
                GetLatestVersion(source, target, itm.ChangesetId);

                var status = Tfs.CheckMerge(source, target, itm.ChangesetId);
                if (status.NumConflicts > 0)
                    yield return itm.ChangesetId;
            }
        }

        public void OneMerge(Form frm, IEnumerable<UIChangeset> allCandidates, string source, string target, UIChangeset[] candidates, int? taskId, bool tryAutoMerge = false, bool takeSource = false)
        {
            if (!candidates.Any())
                throw new ArgumentException("OMerge: candidates.Count = 0");

            if (!IsCanOneMerge(allCandidates, candidates))
            {
                MessageBox.Show("Changesets must be successively!");
                return;
            }

            var changesetStart = candidates.OrderBy(x => x.ChangesetId).FirstOrDefault();
            if (changesetStart == null)
                return;

            var changesetEnd = candidates.OrderByDescending(x => x.ChangesetId).FirstOrDefault();
            if (changesetEnd == null)
                return;

            foreach (var itm in candidates)
                GetLatestVersion(source, target, itm.ChangesetId);
            
            Tfs.Merge(source, target, changesetStart.ChangesetId, changesetEnd.ChangesetId);
            var comment = string.Join("; ", candidates.Select(x => x.Comment));

            // !!!! add dialog for edit comment
            PostMerge(frm, target, comment, taskId, tryAutoMerge, takeSource);
        }

        public bool IsCanOneMerge(IEnumerable<UIChangeset> allCandidates, UIChangeset[] candidates)
        {
            if (candidates.Count() <= 1)
                return true;
      
            var all = allCandidates.OrderBy(x => x.ChangesetId)
                .Select(x => x.ChangesetId);

            var part = candidates.OrderBy(x => x.ChangesetId)
                .Select(x => x.ChangesetId).ToArray();

            var maxPart = part.Max();
            var minPart = part.Min();

            return all.Where(x => x >= minPart && x <= maxPart).SequenceEqual(part);
        }

        public void HMerge(Form frm, HMergeItem[] mergePath, int? taskId)
        {
            if (mergePath.FirstOrDefault(x => !x.ChangesetId.HasValue) == null)
                return;

            var arr = mergePath.ToArray();
            for (int i = 0; i < arr.Length - 1; i++)
            {
                var source = arr[i];
                var target = arr[i + 1];

                if (!source.ChangesetId.HasValue)
                    break;

                if (target.ChangesetId.HasValue)
                    continue;

                var changeset = Tfs.GetChangeset(source.ChangesetId.Value);
                if (changeset == null)
                    break;

                GetLatestVersion(source.Branch, target.Branch, changeset.ChangesetId);

                Tfs.Merge(source.Branch, target.Branch, changeset.ChangesetId);
                var targetChangesetId = PostMerge(frm, target.Branch, changeset.Comment, taskId, false, false);
                if (!targetChangesetId.HasValue)
                    break;

                target.SetChangesetId(targetChangesetId.Value);
            }
        }

        public void Merge(Form frm, string source, string target, UIChangeset[] candidates, int? taskId, bool tryAutoMerge = false, bool takeSource = false)
        {
            if (!candidates.Any())
                throw new ArgumentException("Merge: candidates.Count = 0");

            foreach (var itm in candidates)
            {
                GetLatestVersion(source, target, itm.ChangesetId);

                Tfs.Merge(source, target, itm.ChangesetId);
                if (PostMerge(frm, target, itm.Comment, taskId, tryAutoMerge, takeSource).HasValue == false)
                    break;
            }
        }

        private void GetLatestVersion(string source, string target, int changesetId)
        {
            var changeset = Tfs.GetChangeset(changesetId);
            if (changeset == null)
                return;

            var serverItems = changeset.Changes
                .Select(x => x.Item.ServerItem.Replace(source, target))
                .ToArray();

            if (serverItems.Length > 0)
                Tfs.Get(serverItems, VersionSpec.Latest, RecursionType.None, GetOptions.Overwrite);
        }

        private int? PostMerge(Form frm, string target, string comment, int? taskId, bool tryAutoMerge, bool takeSource)
        {
            int? changesetId = null;
            var conflicts = Tfs.GetConflicts(target);

            if (conflicts.Length > 0)
            {
                if (tryAutoMerge)
                {
                    foreach (var conflict in conflicts)
                        Tfs.AutoMergeResolveConflict(conflict);

                    conflicts = Tfs.GetConflicts(target);
                }

                if (takeSource)
                {
                    foreach (var conflict in conflicts)
                        Tfs.TakeSourceResolveConflict(conflict);

                    conflicts = Tfs.GetConflicts(target);
                }

                if (conflicts.Length > 0)
                    conflicts = ResolveConflicts(frm, target, conflicts);
            }

            if (conflicts.Length == 0)
            {
                try
                {
                    changesetId = Tfs.CheckIn(target, comment, taskId);
                }
                catch (Exception)
                {
                    conflicts = Tfs.GetConflicts(target);
                    if (conflicts.Length > 0)
                    {
                        conflicts = ResolveConflicts(frm, target, conflicts);
                        if (conflicts.Length == 0)
                            changesetId = Tfs.CheckIn(target, comment, taskId);
                        else
                        {
                            Tfs.Undo(target);
                        }
                    }
                    else
                    {
                        Tfs.Undo(target);
                        throw;
                    }
                }
            }
            else
            {
                Tfs.Undo(target);
            }

            return changesetId;
        }

        private Conflict[] ResolveConflicts(Form frm, string target, Conflict[] conflicts)
        {
            Tfs.ShowConflictsDialog(frm, conflicts);
            return Tfs.GetConflicts(target);
        }


        public void ShowTrackView(Form frm, string changesetId)
        {
            int? id = Converter.Str2Int(changesetId);
            if (id == null)
                return;

            Tfs.ShowTrackView(frm, id.Value);
        }
    }
}
